import numpy as np
import pandas as pd
import CalModule
import networkx as nx

from settings import *

MIN_DIST_ROUTE = 1e-10
MAX_DIST_ROUTE = 1


def get_graph(lastpoint, thispoint, search_radius, conn, datatable):
    lon = (thispoint.lon+lastpoint.lon) / 2
    lat = (thispoint.lat+lastpoint.lat) / 2
    cmdStr = f"""
        select id, snodeid, enodeid, rang, direction,geom,SPDLMTE2S, SPDLMTS2E,length_deg from {datatable} where 
        st_intersects(
            geom,
            st_buffer(
                st_geomFromText(
                    'Point({lon} {lat})', {SRID}
                ), 
                {search_radius}
            )
        )
        """
    df = pd.read_sql(cmdStr, conn)
    segments_azi_rad = CalModule.d2r(df['rang'].values)

    # 建立邻接表
    graph = nx.DiGraph()

    print("graph_edge_count:", df.shape[0])

    # 用dict记录每个路段的长度、方位角，供后续快速查询
    path_dict = {}
    for i in range(df.shape[0]):
        edgeid = df['id'].iloc[i]
        path_dict[edgeid] = (df.iloc[i]['length_deg'], segments_azi_rad[i])
        snodeid = df['snodeid'].iloc[i]
        enodeid = df['enodeid'].iloc[i]
        edge_len_deg = df['length_deg'].iloc[i]
        direction = df['direction'].iloc[i]
        if direction == '1':  # one way, from snode to enode
            graph.add_edge(snodeid, enodeid, weight=edge_len_deg, edgeid=edgeid)
        else:  # two way, s->e and e->s
            if CalModule.is_obtuse_angle(df['rang'].iloc[i] - thispoint.p_ang):  # e->s
                graph.add_edge(enodeid, snodeid, weight=edge_len_deg, edgeid=edgeid)
                path_dict[edgeid] = (df.iloc[i]['length_deg'], segments_azi_rad[i] + np.pi)  # update path_dict
            else:  # s->e
                graph.add_edge(snodeid, enodeid, weight=edge_len_deg, edgeid=edgeid)
    return graph, path_dict


def get_from_nodes(df, lastpoint):
    """
    get source nodes for shortest path finding

    Parameters
    ----------
    df
    lastpoint

    Returns
    -------

    """
    # 上一个点的方位角
    p_ang = lastpoint.p_ang
    from_nodes = []
    len_from_list = []
    from_edges = df['id'].values
    for i in range(len(df)):
        direction = df['direction'].iloc[i]
        snodeid = df['snodeid'].iloc[i]
        enodeid = df['enodeid'].iloc[i]
        pt_list = list(df['geom'].iloc[i].coords)
        if direction == '1':
            from_node = enodeid
        else:
            if CalModule.is_obtuse_angle(df['rang'].iloc[i] - p_ang):  # e->s
                from_node = snodeid
                pt_list.reverse()
            else:
                from_node = enodeid
        from_nodes.append(from_node)

        # 计算lastpoint到路段起点的距离len_from0。len_from为lastpoint到路段终点距离，故len_from = len_edge - len_from0
        _, _, len_from0, _, _ = CalModule.projection_pl(lastpoint.lon, lastpoint.lat, pt_list)
        len_from_list.append(df['length_deg'].iloc[i] - len_from0)  # degree
    return from_nodes, len_from_list, from_edges


def get_to_nodes(df, thispoint):
    """
    get target nodes for shortest path finding

    Parameters
    ----------
    df
    thispoint

    Returns
    -------

    """
    p_ang = thispoint.p_ang
    to_nodes = []
    len_to_list = []
    to_edge = df['id'].values
    for i in range(len(df)):
        direction = df['direction'].iloc[i]
        snodeid = df['snodeid'].iloc[i]
        enodeid = df['enodeid'].iloc[i]
        pt_list = list(df['geom'].iloc[i].coords)
        if direction == '1':
            to_node = snodeid
        else:
            if CalModule.is_obtuse_angle(df['rang'].iloc[i] - p_ang):  # e->s
                to_node = enodeid
                pt_list.reverse()
            else:  # s->e
                to_node = snodeid
        to_nodes.append(to_node)
        _, _, len_to, _, _ = CalModule.projection_pl(thispoint.lon, thispoint.lat, pt_list)
        len_to_list.append(len_to)
    return to_nodes, len_to_list, to_edge


def compute_path_length(lastpoint, thispoint, df_last, df_this, conn, datatable):
    # 求lastpoint thispoint对应的出口点和入口点
    from_nodes, len_from_point_list, from_edge = get_from_nodes(df_last, lastpoint)
    to_nodes, len_to_point_list, to_edge = get_to_nodes(df_this, thispoint)
    n_from = len(from_nodes)
    n_to = len(to_nodes)

    # 求路径距离(path_len) 详见文档
    # path_len = lastpoint到出口点(from_node)的距离+中间经过的距离(shortest)+入口点(to_point)到thispoint的距离
    dist_between_point = CalModule.cal_dist(lastpoint.lon, lastpoint.lat, thispoint.lon, thispoint.lat)
    if dist_between_point < 0.0003:
        dist_between_point = 0.0003
    radius_ratio = 1.5
    search_radius = dist_between_point * radius_ratio

    graph, path_dict = get_graph(lastpoint, thispoint, search_radius, conn, datatable)

    # 初始化路径距离矩阵
    path_len = np.mat(np.ones((n_from, n_to)) * MAX_DIST_ROUTE)  # 默认为不可达
    # 初始化转移路径矩阵（记录从每个from_node到每个to_node经过的路段id）
    path_array = [[[] for i in range(n_to)] for j in range(n_from)]
    for i in range(n_from):
        from_node = from_nodes[i]
        if not graph.adj.get(from_node):  # from_node不在图中或没有出度（可能是前后两点在同一路段上）
            for j in range(n_to):
                if from_edge[i] == to_edge[j]:  # 前后两点在同一路段上，形成环
                    edge_len = df_last.iloc[i]['length_deg']
                    path_len[i, j] = np.max([MIN_DIST_ROUTE, len_to_point_list[j] + len_from_point_list[i] - edge_len])  # prevent zero division
                    path_array[i][j] = [from_edge[i], from_edge[i]]
                else:  # 不可达，将断裂的path直接保存
                    path_array[i][j] = [from_edge[i], to_edge[j]]
                    path_len[i, j] = MAX_DIST_ROUTE
        else:
            for j in range(n_to):
                to_node = to_nodes[j]
                if from_edge[i] == to_edge[j]:
                    # from to是同一条边的两个节点 from是前一点的出点，所以边的方向是to-->lastpoint-->thispoint-->from
                    edge_len = df_last.iloc[i]['length_deg']
                    len_temp = len_to_point_list[j] + len_from_point_list[i] - edge_len
                    path_array[i][j] = [from_edge[i], from_edge[i]]
                    if len_temp >= 0:  # 不知道为什么出现过谜之负值
                        path_len[i, j] = np.max([MIN_DIST_ROUTE, len_temp])
                    else:  # to-->thispoint-->lastpoint-->from, 实际上是逆行了，不可以
                        path_len[i, j] = MAX_DIST_ROUTE
                else:
                    try:
                        shortest_len = nx.astar_path_length(graph, source=from_node, target=to_node, weight='weight')
                        path = get_path_edges(graph, from_node, to_node)
                        path_len[i, j] = np.max([MIN_DIST_ROUTE, len_from_point_list[i] + shortest_len + len_to_point_list[j]])
                        path_array[i][j] = [from_edge[i]] + path + [to_edge[j]]
                    except (nx.NetworkXNoPath, nx.NodeNotFound):  # 不连通
                        path_len[i, j] = MAX_DIST_ROUTE
                        path_array[i][j] = [from_edge[i], to_edge[j]]

    return path_len, path_array, path_dict


def get_path_edges(graph, from_node, to_node):
    path_nodes_list = nx.astar_path(graph, source=from_node, target=to_node, weight='weight')
    path_edges = []
    for i in range(len(path_nodes_list) - 1):
        path_edges.append(graph.get_edge_data(path_nodes_list[i], path_nodes_list[i + 1])['edgeid'])
    return path_edges
